<template>
  <div class="inner-html-container">
    <div class="page">
      <div class="tips">
        <div class="title">DeepSeek-GPT</div>
        <div class="desc" v-if="!isMobile">
          该应用采取本地缓存的方式，不保留任何用户有关数据，请文明使用！
          <el-button
            type="text"
            @click="handleUpdateSystemPrompt"
            style="margin-left: 10px; color: #fff;"
          >
            修改预设
          </el-button>
        </div>
        <div @click="handleClearStorage" v-else class="pointer">清空</div>
      </div>
      <div class="grid-space-between" :class="!isMobile ? 'grid-box' : ''">
        <div class="left-container" v-if="!isMobile">
          <el-button
            type="success"
            style="width: 100%"
            @click="handleAddSession"
            >新建对话</el-button
          >
          <div class="session-area">
            <div
              class="session-item"
              :class="activeIndex == index ? 'session-item-active' : ''"
              v-for="(item, index) in sessionList"
              :key="index"
              @click="handleChangeSessionIndex(index)"
            >
              <span
                :class="activeIndex == index ? 'active-node' : 'normal-node'"
                v-if="editIndex != index"
                >{{ item.title }}</span
              >
              <el-input
                :ref="`renameRef_${index}`"
                autofocus
                v-model="item.title"
                velse
                size="small"
                style="width: 120px"
                @blur="editIndex = -1"
                @change="editIndex = -1"
              />
              <div class="icon-box">
                <!-- 重命名 -->
                <el-icon
                  class="icon"
                  color="#E6A23C"
                  @click.stop="handleFocusInput(index)"
                >
                  <BrushFilled />
                </el-icon>
                <!-- 删除 -->
                <el-icon
                  class="icon"
                  color="#F56C6C"
                  @click.stop="handleDeleteSession(index)"
                  ><DeleteFilled
                /></el-icon>
              </div>
            </div>
          </div>
        </div>
        <div class="container">
          <div class="message-area">
            <MessageComp
              ref="messageRef"
              :message="queryInfos.messages"
            ></MessageComp>
          </div>
       
          <div class="input-area" :class="isMobile ? 'left-space' : ''">
            <el-input
              v-model="queryKeys"
              id="keyInput"
              placeholder="请输入内容"
              @keyup.enter.native="handleEnterPress"
            />
            <el-button
              style="height: 40px"
              type="primary"
              @click="handleRequest"
              :disabled="!queryKeys"
              :loading="loading"
            >
              <el-icon><Promotion /></el-icon>
            </el-button>
          </div>
        </div>
      </div>
    </div>
    <el-dialog
      title="修改预设"
      v-model="showSystemPromptDialog"
      width="50%"
      :modal-append-to-body="false"
      append-to-body
      custom-class="system-prompt-dialog"
    >
    <div style="color: red;margin-bottom: 10px;text-align: center;">修改预设需要重新新建对话</div>
      <el-input
        type="textarea"
        v-model="defaultSystemPrompt"
        :rows="10"
        placeholder="请输入系统预设提示词"
      />
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="showSystemPromptDialog = false">取消</el-button>
          <el-button type="primary" @click="saveSystemPrompt">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, nextTick, watch } from 'vue'
import OpenAI from "openai"
import MessageComp from "./components/messageComp.vue"
import { Promotion, DeleteFilled, BrushFilled } from "@element-plus/icons-vue"
import { getTokens } from "@/api/modules/deepseek.js"
import { ElMessage, ElMessageBox } from "element-plus"
import MobileDetect from "mobile-detect"

// 响应式状态
const isMobile = ref(false)
const renameValue = ref("")
const sessionList = ref([])
const activeIndex = ref(-1)
const editIndex = ref(-1)
const totalAmt = ref(0)
const queryKeys = ref("")
const openai = ref(null)
const loading = ref(false)
const showSystemPromptDialog = ref(false)
const tempSystemPrompt = ref('')
const messageRef = ref(null)

// API 配置
const apiKey = "sk-spexidclegtdmbhmrqyxlwcffttfjzhzisctnivoqqhihkka"

const defaultSystemPrompt = ref(`你是一位专业的编程助手。在回答代码相关问题时，请严格遵循以下规范：
1. 代码展示格式：
- 使用 Markdown 代码块
- 每个代码块都必须指定语言（如 \`\`\`javascript）
- 代码必须格式化，包含适当的换行和缩进
- 每行代码不要太长，适当换行
- 添加必要的注释说明  
2. 代码说明：
- 简要说明代码的功能和特点
- 列出关键的优化点
- 说明时间和空间复杂度
- 必要时提供使用示例
请严格按照这个格式输出代码，确保代码的可读性。`)

const queryInfos = reactive({
  messages: [],
  model: "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
  stream: true,
  max_tokens: 2000,
  temperature: 0.7,
  presence_penalty: 0,
  frequency_penalty: 0,
  top_p: 0.9,
  reasoning: false,
  thinking_enabled: false,
})

// 初始化函数
const initSessionList = () => {
  sessionList.value = JSON.parse(localStorage.getItem("list") || "[]")
}

const initIndex = () => {
  const listLen = JSON.parse(localStorage.getItem("list") || "[]").length
  const lastIndex = JSON.parse(localStorage.getItem("index") || "-1")
  
  if (listLen) {
    activeIndex.value = lastIndex || 0
  } else {
    activeIndex.value = -1
  }
  
  if (activeIndex.value != -1) {
    queryInfos.messages = sessionList.value[activeIndex.value].messages || []
  }
}

const initOpenAI = () => {
  openai.value = new OpenAI({
    baseURL: "https://api.siliconflow.cn/v1/",
    apiKey: apiKey,
    dangerouslyAllowBrowser: true,
    model: "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
    thinking_enabled: false,
  })
}

const initToken = async () => {
  const res = await getTokens({
    deepseek: "Y",
    gptToken: apiKey,
  })
  const { balance_infos = [] } = res
  totalAmt.value = balance_infos.reduce((sum, item) => sum + Number(item.total_balance), 0)
}

// 方法
const handleClearStorage = () => {
  localStorage.removeItem("list")
  localStorage.removeItem("index")
  queryInfos.messages = []
  sessionList.value = []
  activeIndex.value = -1
}

const handleAddSession = () => {
  if (loading.value) {
    ElMessage({ type: "warning", message: "请当前问题查询完成后重试！" })
    return
  }

  const systemPrompt = {
    role: "system",
    content: defaultSystemPrompt.value
  }

  sessionList.value.push({
    title: `对话${sessionList.value.length + 1}`,
    crtTime: new Date(),
    messages: [systemPrompt],
  })

  queryInfos.messages = [systemPrompt]
  activeIndex.value = sessionList.value.length - 1
}

const handleDeleteSession = (index = 0) => {
  ElMessageBox.confirm("确认删除当前对话？", "警告", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    // 删除指定的对话
    sessionList.value.splice(index, 1)
    /**
     * 逻辑1:删除的当前选中的对话框，判断新的当前元素（原来的+1）是否存在，不存在则向前抓
     * 逻辑2:删除的非当前选中的(只判断小于，因为大于不影响当前展示内容)
     */
    if (index == activeIndex.value) {
      // 初始化index
      activeIndex.value = sessionList.value[index] ? index : --index
    } else if (index < activeIndex.value) {
      activeIndex.value = --activeIndex.value
    }
    // 更新页面数据展示
    queryInfos.messages =
      activeIndex.value > -1 ? sessionList.value[activeIndex.value] : []
    handleChangeSessionIndex(activeIndex.value)
  }).catch((action) => {
  })
}

const handleFocusInput = (index) => {
  const _this = this
  editIndex.value = index
  nextTick(() => {
    _this.$refs[`renameRef_${index}`][0].focus()
  })
}

const handleChangeSessionIndex = (index) => {
  if (loading.value) {
    ElMessage({ type: "warning", message: "请当前问题查询完成后重试！" })
    return
  }
  activeIndex.value = index
  queryInfos.messages =
    sessionList.value[activeIndex.value]?.messages || []
}

const handleRequest = async () => {
  if (!queryKeys.value) return
  if (!openai.value) initOpenAI()
  // 查询当前是否存在对话窗口，不存在的话，新建一个对话窗口
  if (!sessionList.value.length) {
    await handleAddSession()
  }
  // 1.向对话窗口添加对话信息(请求)
  queryInfos.messages.push({
    role: "user",
    content: queryKeys.value,
  })
  queryKeys.value = null
  
  // 添加一个临时的思考中提示
  queryInfos.messages.push({
    role: "assistant",
    content: "思考中...",
  })
  messageRef.value.scrollBottom()

  try {
    loading.value = true
    const response = await openai.value.chat.completions.create(queryInfos)
    
    // 清空之前的思考中提示
    queryInfos.messages[queryInfos.messages.length - 1].content = ""
    
    let currentContent = ""
    let isInCodeBlock = false
    let codeLanguage = ""
    let isFirstReasoning = true
   
    // 逐步处理响应
    for await (const part of response) {
      const content2 = part.choices[0].delta
      console.log(content2)

      // 检查是否包含推理过程
      if (content2.reasoning_content) {
        // 第一次收到推理内容时添加标题
        if (isFirstReasoning && content2.reasoning_content.trim()) {
          currentContent += "思考过程：\n"
          isFirstReasoning = false
        }
        // 处理推理内容，去除多余的换行
        const cleanedContent = content2.reasoning_content
          .replace(/\n+/g, '\n')  // 将多个换行符替换为单个
          .trim()                 // 去除首尾空白
        
        if (cleanedContent) {
          currentContent += cleanedContent
        }
        
        queryInfos.messages[queryInfos.messages.length - 1].content = currentContent
        messageRef.value.scrollBottom()
        continue
      }

      if (part.choices?.[0]?.delta?.content) {
        // 如果是第一个非推理内容，添加回答标记
        if (!isFirstReasoning && !currentContent.includes("\n回答：")) {
          currentContent += "\n\n回答：\n"
        }

        const content = part.choices[0].delta.content
       
        // 检测代码块开始
        if (content.includes("```") && !isInCodeBlock) {
          isInCodeBlock = true
          const languageMatch = content.match(/```([a-zA-Z0-9]+)/)
          if (languageMatch) {
            codeLanguage = languageMatch[1]
            // 添加代码块开始标记和语言
            currentContent += "```" + codeLanguage + "\n"
            // 移除已处理的代码块标记和语言
            const remainingContent = content.slice(content.indexOf(codeLanguage) + codeLanguage.length)
            currentContent += remainingContent
          } else {
            // 没有指定语言的代码块
            currentContent += "```\n"
            currentContent += content.replace("```", "")
          }
        }
        // 检测代码块结束
        else if (content.includes("```") && isInCodeBlock) {
          isInCodeBlock = false
          currentContent += "\n```\n"
          // 添加代码块后的剩余内容
          const remainingContent = content.split("```")[1]
          if (remainingContent) {
            currentContent += remainingContent
          }
        }
        // 正常内容添加
        else {
          currentContent += content
        }
        
        // 更新消息内容
        queryInfos.messages[queryInfos.messages.length - 1].content = currentContent
        messageRef.value.scrollBottom()
      }
    }
    
    loading.value = false
    sessionList.value[activeIndex.value].messages = queryInfos.messages
  } catch (error) {
    // 发生错误时也要更新最后一条消息
    queryInfos.messages[queryInfos.messages.length - 1].content = "加载失败,请稍后重试!"
    loading.value = false
    console.error("Error:", error)
  }
}

const handleUpdateSystemPrompt = () => {
  tempSystemPrompt.value = defaultSystemPrompt.value
  showSystemPromptDialog.value = true
}

const saveSystemPrompt = () => {
  console.log(tempSystemPrompt.value)
  console.log(defaultSystemPrompt.value)
  localStorage.setItem('systemPrompt', defaultSystemPrompt.value)
  showSystemPromptDialog.value = false
}

const handleEnterPress = (event) => {
  // 如果按下 shift + enter，则换行
  if (event.shiftKey) {
    return
  }
  // 如果正在加载或没有输入内容，则不处理
  if (loading.value || !queryKeys.value) {
    return
  }
  // 阻止默认换行行为
  event.preventDefault()
  // 触发请求
  handleRequest()
}

// 监听器
watch(sessionList, (val) => {
  const list = val.map((o, i) => {
    if (i == activeIndex.value) {
      return {
        ...o,
        messages: queryInfos.messages,
      }
    }
    return { ...o }
  })
  localStorage.setItem("list", JSON.stringify(list))
}, { deep: true })

watch(activeIndex, (val) => {
  localStorage.setItem("index", JSON.stringify(val))
}, { deep: true })

// 生命周期钩子
onMounted(() => {
  initSessionList()
  initIndex()
  initOpenAI()
  initToken()
  
  const md = new MobileDetect(window.navigator.userAgent)
  isMobile.value = md.mobile()
  
  const savedPrompt = localStorage.getItem('systemPrompt')
  if (savedPrompt) {
    defaultSystemPrompt.value = savedPrompt
  }
})
</script>

<style scoped lang="scss">
.pointer {
  cursor: pointer;
}
.inner-html-container {
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgb(216.8, 235.6, 255);
  .page {
    width: 86vw;
    height: 90vh;
    background: #fff;
    box-shadow: 0 0 12px 12px rgb(197.7, 225.9, 255);
    box-sizing: border-box;
    border-radius: 8px;
    overflow: hidden;
    .tips {
      width: 100%;
      height: 32px;
      background: rgb(121.3, 187.1, 255);
      color: #fff;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0 12px;
      box-sizing: border-box;
      .title {
        font-size: 16px;
      }
      .desc {
        font-size: 14px;
        color: rgb(216.8, 235.6, 255);
      }
    }
    .grid-box {
      display: grid;
      grid-template-columns: 24% auto;
      column-gap: 12px;
    }
    .grid-space-between {
      width: 100%;
      height: calc(100% - 48px);
      .left-container {
        background-color: #f7f8fa;
        padding: 12px;
        height: calc(90vh - 32px - 24px); // 顶部比标题、padding边框
        // 对话列表的展示区域
        .session-area {
          width: 100%;
          height: calc(90vh - 32px - 24px - 12px - 32px); // 边框+按钮
          margin-top: 12px;
          box-sizing: border-box;
          overflow-y: auto;
          .session-item {
            height: 36px;
            line-height: 36px;
            margin-bottom: 8px;
            border-radius: 4px;
            padding: 0 18px;
            background: rgb(199.5, 201, 204);
            color: #f4f5f5;
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: pointer;
            .normal-node {
              padding-left: 14px;
            }
            .icon-box {
              visibility: hidden;
              display: grid;
              column-gap: 8px;
              grid-template-columns: repeat(2, 1fr);
              .icon {
                cursor: pointer;
              }
            }
            &:hover .icon-box {
              visibility: visible;
            }
          }
          .session-item-active {
            background: rgb(51.2, 126.4, 204);
            color: #f4f5f5;
            .active-node {
              display: flex;
              align-items: center;
              &::before {
                display: inline-block;
                content: "";
                width: 6px;
                height: 6px;
                background: #fff;
                border-radius: 50%;
                margin-right: 8px;
              }
            }
          }
        }
      }
      .container {
        width: 100%;
        height: calc(90vh - 32px);
        background: #fff;
        border-radius: 8px;
        .message-area {
          height: calc(100% - 40px - 16px - 44px);
          width: calc(100% - 32px);
          padding: 16px 16px 0 16px;
        }
        .user-tokens {
          height: 30px;
          line-height: 30px;
          padding: 0 8px 0 0;
          font-size: 14px;
          color: #c7c8ca;
          user-select: none;
        }
        // 手机端留左侧边框
        .left-space {
          margin-left: 16px;
        }
        .input-area {
          height: 40px;
          display: grid;
          grid-template-columns: auto 90px;
          box-sizing: border-box;
          background: #fff;
          padding: 0 16px 16px 0;
        }
      }
    }
  }
}

.system-prompt-dialog {
  margin-top: 5vh !important;
  
  .el-dialog__body {
    padding: 20px;
    max-height: 60vh;
    overflow-y: auto;
  }
  
  .el-textarea__inner {
    font-family: monospace;
    line-height: 1.6;
  }
  
  .dialog-footer {
    padding-top: 20px;
    text-align: right;
  }
}

// 确保弹框不会被其他元素遮挡
.el-overlay {
  z-index: 2000;
}
</style>


